/*
 * Copyright 2014 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package io.netty.util.concurrent;

import io.netty.util.internal.InternalThreadLocalMap;
import io.netty.util.internal.PlatformDependent;

import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Set;

/**
 * A special variant of {@link ThreadLocal} that yields higher access performance when accessed from
 * a {@link FastThreadLocalThread}.
 * <p>
 * Internally, a {@link FastThreadLocal} uses a constant index in an array, instead of using hash
 * code and hash table, to look for a variable.  Although seemingly very subtle, it yields slight
 * performance advantage over using a hash table, and it is useful when accessed frequently.
 * </p><p>
 * To take advantage of this thread-local variable, your thread must be a {@link
 * FastThreadLocalThread} or its subtype. By default, all threads created by {@link
 * DefaultThreadFactory} are {@link FastThreadLocalThread} due to this reason.
 * </p><p>
 * Note that the fast path is only possible on threads that extend {@link FastThreadLocalThread},
 * because it requires a special field to store the necessary state.  An access by any other kind of
 * thread falls back to a regular {@link ThreadLocal}.
 * </p>
 *
 * @param <V> the type of the thread-local variable
 * @see ThreadLocal
 */
public class FastThreadLocal<V> {

  private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();

  /**
   * Removes all {@link FastThreadLocal} variables bound to the current thread.  This operation is
   * useful when you are in a container environment, and you don't want to leave the thread local
   * variables in the threads you do not manage.
   */
  public static void removeAll() {
    InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();
    if (threadLocalMap == null) {
      return;
    }

    try {
      Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
      if (v != null && v != InternalThreadLocalMap.UNSET) {
        @SuppressWarnings("unchecked")
        Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;
        FastThreadLocal<?>[] variablesToRemoveArray =
            variablesToRemove.toArray(new FastThreadLocal[0]);
        for (FastThreadLocal<?> tlv : variablesToRemoveArray) {
          tlv.remove(threadLocalMap);
        }
      }
    } finally {
      InternalThreadLocalMap.remove();
    }
  }

  /**
   * Returns the number of thread local variables bound to the current thread.
   */
  public static int size() {
    InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();
    if (threadLocalMap == null) {
      return 0;
    } else {
      return threadLocalMap.size();
    }
  }

  /**
   * Destroys the data structure that keeps all {@link FastThreadLocal} variables accessed from
   * non-{@link FastThreadLocalThread}s.  This operation is useful when you are in a container
   * environment, and you do not want to leave the thread local variables in the threads you do not
   * manage.  Call this method when your application is being unloaded from the container.
   */
  public static void destroy() {
    InternalThreadLocalMap.destroy();
  }

  @SuppressWarnings("unchecked")
  private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap,
      FastThreadLocal<?> variable) {
    Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
    Set<FastThreadLocal<?>> variablesToRemove;
    if (v == InternalThreadLocalMap.UNSET || v == null) {
      variablesToRemove = Collections
          .newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());
      threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove);
    } else {
      variablesToRemove = (Set<FastThreadLocal<?>>) v;
    }

    variablesToRemove.add(variable);
  }

  private static void removeFromVariablesToRemove(
      InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {

    Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);

    if (v == InternalThreadLocalMap.UNSET || v == null) {
      return;
    }

    @SuppressWarnings("unchecked")
    Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;
    variablesToRemove.remove(variable);
  }

  private final int index;

  public FastThreadLocal() {
    index = InternalThreadLocalMap.nextVariableIndex();
  }

  /**
   * Returns the current value for the current thread
   */
  @SuppressWarnings("unchecked")
  public final V get() {
    // 这里主要是获取一个InternalThreadLocalMap对象，该对象中维护了一个Object[]，该数组长度为32，
    // 默认从第一位才开始存储PoolArena，如果对应的位置没有存储数据，那么就会使用InternalThreadLocalMap.UNSET
    // 进行填充
    InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
    Object v = threadLocalMap.indexedVariable(index);
    if (v != InternalThreadLocalMap.UNSET) {
      return (V) v;
    }

    // 如果对应的位置是默认值，即InternalThreadLocalMap.UNSET，那么这里会调用其initialize()方法进行初始化，
    // 也即从HeapArena和DirectArena数组中分别获取最少使用的Arena，将其封装到PoolThreadCache中
    V value = initialize(threadLocalMap);
    registerCleaner(threadLocalMap);
    return value;
  }

  private void registerCleaner(final InternalThreadLocalMap threadLocalMap) {
    Thread current = Thread.currentThread();
    if (FastThreadLocalThread.willCleanupFastThreadLocals(current) || threadLocalMap
        .isCleanerFlagSet(index)) {
      return;
    }

    threadLocalMap.setCleanerFlag(index);

    // TODO: We need to find a better way to handle this.
        /*
        // We will need to ensure we will trigger remove(InternalThreadLocalMap) so everything will be released
        // and FastThreadLocal.onRemoval(...) will be called.
        ObjectCleaner.register(current, new Runnable() {
            @Override
            public void run() {
                remove(threadLocalMap);

                // It's fine to not call InternalThreadLocalMap.remove() here as this will only be triggered once
                // the Thread is collected by GC. In this case the ThreadLocal will be gone away already.
            }
        });
        */
  }

  /**
   * Returns the current value for the specified thread local map. The specified thread local map
   * must be for the current thread.
   */
  @SuppressWarnings("unchecked")
  public final V get(InternalThreadLocalMap threadLocalMap) {
    Object v = threadLocalMap.indexedVariable(index);
    if (v != InternalThreadLocalMap.UNSET) {
      return (V) v;
    }

    return initialize(threadLocalMap);
  }

  private V initialize(InternalThreadLocalMap threadLocalMap) {
    V v = null;
    try {
      v = initialValue();
    } catch (Exception e) {
      PlatformDependent.throwException(e);
    }

    threadLocalMap.setIndexedVariable(index, v);
    addToVariablesToRemove(threadLocalMap, this);
    return v;
  }

  /**
   * Set the value for the current thread.
   */
  public final void set(V value) {
    if (value != InternalThreadLocalMap.UNSET) {
      InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
      if (setKnownNotUnset(threadLocalMap, value)) {
        registerCleaner(threadLocalMap);
      }
    } else {
      remove();
    }
  }

  /**
   * Set the value for the specified thread local map. The specified thread local map must be for
   * the current thread.
   */
  public final void set(InternalThreadLocalMap threadLocalMap, V value) {
    if (value != InternalThreadLocalMap.UNSET) {
      setKnownNotUnset(threadLocalMap, value);
    } else {
      remove(threadLocalMap);
    }
  }

  /**
   * @return see {@link InternalThreadLocalMap#setIndexedVariable(int, Object)}.
   */
  private boolean setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
    if (threadLocalMap.setIndexedVariable(index, value)) {
      addToVariablesToRemove(threadLocalMap, this);
      return true;
    }
    return false;
  }

  /**
   * Returns {@code true} if and only if this thread-local variable is set.
   */
  public final boolean isSet() {
    return isSet(InternalThreadLocalMap.getIfSet());
  }

  /**
   * Returns {@code true} if and only if this thread-local variable is set. The specified thread
   * local map must be for the current thread.
   */
  public final boolean isSet(InternalThreadLocalMap threadLocalMap) {
    return threadLocalMap != null && threadLocalMap.isIndexedVariableSet(index);
  }

  /**
   * Sets the value to uninitialized; a proceeding call to get() will trigger a call to
   * initialValue().
   */
  public final void remove() {
    remove(InternalThreadLocalMap.getIfSet());
  }

  /**
   * Sets the value to uninitialized for the specified thread local map; a proceeding call to get()
   * will trigger a call to initialValue(). The specified thread local map must be for the current
   * thread.
   */
  @SuppressWarnings("unchecked")
  public final void remove(InternalThreadLocalMap threadLocalMap) {
    if (threadLocalMap == null) {
      return;
    }

    Object v = threadLocalMap.removeIndexedVariable(index);
    removeFromVariablesToRemove(threadLocalMap, this);

    if (v != InternalThreadLocalMap.UNSET) {
      try {
        onRemoval((V) v);
      } catch (Exception e) {
        PlatformDependent.throwException(e);
      }
    }
  }

  /**
   * Returns the initial value for this thread-local variable.
   */
  protected V initialValue() throws Exception {
    return null;
  }

  /**
   * Invoked when this thread local variable is removed by {@link #remove()}. Be aware that {@link
   * #remove()} is not guaranteed to be called when the `Thread` completes which means you can not
   * depend on this for cleanup of the resources in the case of `Thread` completion.
   */
  protected void onRemoval(@SuppressWarnings("UnusedParameters") V value) throws Exception {
  }
}
